/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Threading;

using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Utils;
using Microsoft.Scripting.Runtime;

namespace Microsoft.Scripting.Ast {

    /// <summary>
    /// Dynamic Language Runtime Compiler.
    /// This part compiles lambdas.
    /// </summary>
    partial class LambdaCompiler {
        // Should this be in TypeGen directly?
        [MultiRuntimeAware]
        private static int _Counter;
        [MultiRuntimeAware]
        private static int _GeneratorCounter;

        private static readonly string[] _GeneratorSigNames = new string[] { "$gen", "$ret" };

        internal void EmitConstantArray<T>(IList<T> array) {
            if (!IsDynamicMethod) {
                Debug.Assert(TypeGen != null);

                Slot fields = TypeGen.AddStaticField(typeof(T[]), "__array$" + Interlocked.Increment(ref _Counter));

                ILGen cctor = TypeGen.TypeInitializer;
                cctor.EmitArray(array);
                fields.EmitSet(cctor);

                fields.EmitGet(_ilg);
            } else {
                _ilg.EmitArray(array);
            }
        }

        /// <summary>
        /// Emits a delegate to the method generated for the LambdaExpression.
        /// May end up creating a wrapper to match the requested delegate type.
        /// </summary>
        /// <param name="lambda">Lambda for which to generate a delegate</param>
        /// <param name="delegateType">Type of the delegate.</param>
        private void EmitDelegateConstruction(LambdaExpression lambda, Type delegateType) {
            ScopeInfo si = _compiler.GetScopeInfo(lambda);
            Debug.Assert(si != null);

            //
            // Emit the lambda itself
            //
            LambdaCompiler lc = Compiler.ProvideLambdaImplementation(this, lambda, si.IsClosure);
            lc = CreateWrapperIfNeeded(lc, lambda, delegateType);
            EmitDelegateConstruction(lc, delegateType, si.IsClosure);
        }

        /// <summary>
        /// If lambda and delegate signatures match, no need to create wrapper, otherwise
        /// we create wrapper and generate code to pass arguments through to the underlying method.
        /// </summary>
        private static LambdaCompiler CreateWrapperIfNeeded(LambdaCompiler lc, LambdaExpression lambda, Type type) {
            Debug.Assert(typeof(Delegate).IsAssignableFrom(type) && type != typeof(Delegate));

            Type[] blockSig = Expression.GetLambdaSignature(lambda);
            MethodInfo invoke = type.GetMethod("Invoke");
            ParameterInfo[] parameters = invoke.GetParameters();

            if (SignaturesMatch(blockSig, parameters)) {
                //
                // No wrapper needed if signatures match
                //
                return lc;
            } else {
                //
                // No match, must create wrapper
                //
                return lc.CreateWrapper(lambda, blockSig, invoke, parameters);
            }
        }

        /// <summary>
        /// Signatures match if the length is the same and they have identical types.
        /// </summary>
        private static bool SignaturesMatch(Type/*!*/[]/*!*/ blockSig, ParameterInfo/*!*/[]/*!*/ parameters) {
            if (blockSig.Length == parameters.Length) {
                for (int i = 0; i < parameters.Length; i++) {
                    if (blockSig[i] != parameters[i].ParameterType) {
                        // different types
                        return false;
                    }
                }
                // same
                return true;
            } else {
                // The delegate must have fewer parameters than lambda (not counting the params array)
                // and must be parameter array
                if (parameters.Length == 0 ||
                    !CompilerHelpers.IsParamArray(parameters[parameters.Length - 1]) ||
                    parameters.Length - 1 > blockSig.Length) {

                    // TODO: Validate this in the factory
                    throw new InvalidOperationException("Wrong delegate type");
                }

                return false;
            }
        }

        /// <summary>
        /// Creates the wrapper for the lambda to match delegate signature.
        /// The wrapper can have parameter array, and so can the lambda.
        /// 
        /// The wrapper will propagate parameters from the wrapper into the method
        /// backing the lambda, extracting them from the wrapper's parameter array
        /// if necessary, and also (if lambda itself takes parameter array) shift the
        /// wrapper's parameter array to pass it down to the lambda's method.
        /// </summary>
        /// <param name="lambda">LambdaExpression compiled by lc that we are building wrapper for.</param>
        /// <param name="lambdaSig">Signature of the lambda (parameters only)</param>
        /// <param name="wrapInvoke">"Invoke" method of the wrapper delegate type.</param>
        /// <param name="wrapSig">The wrapper delegate signature (parameters)</param>
        /// <returns>LambdaCompiler for the wrapper.</returns>
        private LambdaCompiler CreateWrapper(LambdaExpression/*!*/ lambda, Type/*!*/[]/*!*/ lambdaSig,
            MethodInfo/*!*/ wrapInvoke, ParameterInfo/*!*/[]/*!*/ wrapSig) {

            // Must be called on the compiler that originally compiled the lambda.
            Debug.Assert(_scope != null && _scope.Expression == lambda);

            // Lambda must have at least as many parameters as the delegate, not counting the
            // params array
            Debug.Assert(lambdaSig.Length >= wrapSig.Length ||
                         (wrapSig.Length > 0 && CompilerHelpers.IsParamArray(wrapSig[wrapSig.Length - 1]) &&
                          lambdaSig.Length >= wrapSig.Length - 1));

            ConstantPool constants = _constantPool.IsBound ? _constantPool.CopyData() : null;

            // Create wrapper compiler
            LambdaCompiler wlc = CreateLambdaCompiler(
                null, // LambdaExpression
                Method.Name,
                wrapInvoke.ReturnType,
                ReflectionUtils.GetParameterTypes(wrapSig),
                null,                           // parameter names
                constants,
                _closureSlot != null
            );

            Debug.Assert(_firstLambdaArgument == wlc._firstLambdaArgument);

            // Pass the closure through, if any
            if (wlc.ClosureSlot != null) {
                wlc.ClosureSlot.EmitGet(wlc.IL);
            }

            bool paramArray = wrapSig.Length > 0 && CompilerHelpers.IsParamArray(wrapSig[wrapSig.Length - 1]);

            // How many parameters can we simply copy forward?
            int copy = wrapSig.Length;
            if (paramArray) copy--;                // do not copy the last parameter, it is a params array

            int current = 0;                        // the parameter being currently emitted

            //
            // First step is easy, simply copy the parameters that we can copy
            //
            while (current < copy) {
                Slot slot = wlc.GetLambdaArgumentSlot(current);
                slot.EmitGet(wlc.IL);

                // Cast to match the lambda signature, if needed
                wlc.EmitCast(slot.Type, lambdaSig[current]);

                current++;
            }

            //
            // If the wrapper has a parameter array, we need to extract the parameters from it
            //
            if (paramArray) {
                Slot wrapperArray = wlc.GetLambdaArgumentSlot(wrapSig.Length - 1);
                Type elementType = wrapSig[wrapSig.Length - 1].ParameterType.GetElementType();

                int extract = lambdaSig.Length - current;    // we already copied "current" number of parameters
                if (lambda.ParameterArray) extract--;       // do not extract last if the lambda has parameter array too

                int extracting = 0;                         // index into the the wrapper's parameter array

                //
                // Extract the parameters that get passed to underlying method one by one
                // to pass them as actual arguments
                //
                while (extracting < extract) {
                    wrapperArray.EmitGet(wlc.IL);
                    wlc.IL.EmitInt(extracting);
                    wlc.IL.EmitLoadElement(elementType);

                    wlc.EmitCast(elementType, lambdaSig[current]);

                    extracting++; current++;
                }

                //
                // Extract the rest of the array in bulk
                //
                if (lambda.ParameterArray) {
                    wrapperArray.EmitGet(wlc.IL);
                    wlc.IL.EmitInt(extract);               // this is how many we already extracted

                    // Call the helper
                    MethodInfo shifter = typeof(RuntimeHelpers).GetMethod("ShiftParamsArray");
                    shifter = shifter.MakeGenericMethod(elementType);
                    wlc.IL.EmitCall(shifter);

                    // just copied the last parameter
                    current++;
                }
            } else {
                // With no parameter array, we must have dealt with all wrapper's arguments
                Debug.Assert(current == wrapSig.Length);
            }

            // We must be done with all parameters to the lambdas method now
            Debug.Assert(current == lambdaSig.Length);

            MethodInfo method = (MethodInfo)Method;
            wlc.IL.EmitCall(method);
            wlc.EmitCast(method.ReturnType, wrapInvoke.ReturnType);
            wlc.IL.Emit(OpCodes.Ret);
            return wlc;
        }

        /// <summary>
        /// Creates the signature for the actual CLR method to create. The base types come from the
        /// lambda/LambdaExpression (or its wrapper method), this method may pre-pend an argument to hold
        /// closure information (for closing over constant pool or the lexical closure)
        /// </summary>
        private static Type/*!*/[]/*!*/ MakeParameterTypeArray(IList<Type/*!*/>/*!*/ baseTypes, ConstantPool cp, bool closure) {
            Assert.NotNullItems(baseTypes);

            Type[] signature;

            if (cp != null || closure) {
                signature = new Type[baseTypes.Count + 1];
                baseTypes.CopyTo(signature, 1);

                // Add the closure argument
                signature[0] = typeof(Closure);
            } else {
                signature = new Type[baseTypes.Count];
                baseTypes.CopyTo(signature, 0);
            }

            return signature;
        }

        /// <summary>
        /// Creates the signature for the lambda as list of types and list of names separately
        /// </summary>
        private static void ComputeSignature(LambdaExpression lambda, out List<Type> paramTypes, out List<string> paramNames, out string implName) {
            paramTypes = new List<Type>();
            paramNames = new List<string>();

            foreach (ParameterExpression p in lambda.Parameters) {
                paramTypes.Add(p.Type);
                paramNames.Add(p.Name);
            }

            implName = GetGeneratedName(lambda.Name);
        }

        private static string GetGeneratedName(string prefix) {
            return prefix + "$" + Interlocked.Increment(ref _Counter);
        }

        /// <summary>
        /// Defines the method with the correct signature and sets up the context slot appropriately.
        /// </summary>
        internal static LambdaCompiler CreateLambdaCompiler(LambdaCompiler outer, LambdaExpression lambda, bool closure) {
            List<Type> paramTypes;
            List<string> paramNames;
            string implName;

            // Create the constant pool, if needed
            ConstantPool cp = outer._dynamicMethod ? new ConstantPool() : null;

            // Create the signature
            ComputeSignature(lambda, out paramTypes, out paramNames, out implName);

            // Create the new method & setup its locals
            return outer.CreateLambdaCompiler(lambda, implName, lambda.ReturnType, paramTypes, paramNames.ToArray(), cp, closure);
        }

        // Used by Compiler, TreeCompiler
        internal void EmitBody() {
            if (_generatorInfo != null) {
                EmitGeneratorLambdaBody();
            } else {
                EmitLambdaBody();
            }
        }

        private void EmitLambdaBody() {
            Debug.Assert(_scope.Expression is LambdaExpression);
            LambdaExpression lambda = (LambdaExpression)_scope.Expression;

            _scope.Enter(this);

            EmitLambdaStart(lambda);

            if (lambda.ReturnType == typeof(void)) {
                EmitExpressionAsVoid(lambda.Body);
                EmitLambdaEnd(lambda);
                EmitReturn();
            } else {
                if (lambda.Body.Type != typeof(void)){
                    EmitReturn(lambda.Body);
                    EmitLambdaEnd(lambda);
                } else {
                    EmitExpressionAsVoid(lambda.Body);
                    EmitLambdaEnd(lambda);

                    if (TypeUtils.CanAssign(typeof(object), lambda.ReturnType)) {
                        _ilg.EmitNull();
                    } else {
                        _ilg.EmitMissingValue(lambda.ReturnType);
                    }

                    EmitReturn();
                }
            }
            
            
            _scope.Exit();
        }

        #region DebugMarkers

        private void EmitLambdaStart(LambdaExpression lambda) {
            if (!_emitDebugSymbols) {
                return;
            }

            // get the source file & language id, if present, otherwise use the
            // information from the parent lambda
            SourceFileInformation fileInfo;
            if (lambda.Annotations.TryGet<SourceFileInformation>(out fileInfo)) {
                Debug.Assert(_typeGen != null);

                _debugSymbolWriter = _typeGen.AssemblyGen.ModuleBuilder.DefineDocument(
                    fileInfo.FileName,
                    fileInfo.LanguageGuid,
                    fileInfo.VendorGuid,
                    SymbolGuids.DocumentType_Text
                );
            }

            // ensure a break point exists at the top
            // of the file if there isn't a statement
            // flush with the start of the file.
            SourceSpan lambdaSpan;
            if (!lambda.Annotations.TryGet<SourceSpan>(out lambdaSpan)) {
                return;
            }

            SourceSpan bodySpan = lambda.Body.Annotations.Get<SourceSpan>();
            if (bodySpan.Start.IsValid) {
                if (bodySpan.Start != lambdaSpan.Start) {
                    EmitPosition(lambdaSpan.Start, lambdaSpan.Start);
                }
            } else {
                Block body = lambda.Body as Block;
                if (body != null) {
                    for (int i = 0; i < body.Expressions.Count; i++) {
                        bodySpan = body.Expressions[i].Annotations.Get<SourceSpan>();
                        if (bodySpan.Start.IsValid) {
                            if (bodySpan.Start != lambdaSpan.Start) {
                                EmitPosition(lambdaSpan.Start, lambdaSpan.Start);
                            }
                            break;
                        }
                    }
                }
            }
        }

        private void EmitLambdaEnd(LambdaExpression lambda) {
            if (!_emitDebugSymbols) {
                return;
            }

            // ensure we emit a sequence point at the end
            // so the user can inspect any info before exiting
            // the function.  Also make sure additional code
            // isn't associated with this function.           
            SourceSpan span;
            if (lambda.Annotations.TryGet<SourceSpan>(out span)) {
                EmitPosition(span.End, span.End);
            }
            EmitSequencePointNone();
        }

        #endregion


        #region GeneratorLambdaExpression

        /// <summary>
        /// Defines the method with the correct signature and sets up the context slot appropriately.
        /// </summary>
        /// <returns></returns>
        private LambdaCompiler CreateGeneratorLambdaCompiler(LambdaExpression block) {
            // Create the GenerateNext function
            LambdaCompiler ncg = CreateLambdaCompiler(
                block,
                GetGeneratorMethodName(block.Name),     // Method Name
                typeof(bool),                           // Return Type
                new Type[] {                            // signature
                    typeof(Generator),
                    typeof(object).MakeByRefType()
                },
                _GeneratorSigNames,                     // param names
                _dynamicMethod ? new ConstantPool() : null,
                _scope.HasEnvironment || _scope.IsClosure
            );

            // We are emitting generator, mark the Compiler
            ncg.IsGeneratorBody = true;

            return ncg;
        }

        /// <summary>
        /// Emits the body of the function that creates a Generator object.  Also creates another
        /// Compiler for the inner method which implements the user code defined in the generator.
        /// </summary>
        private void EmitGeneratorLambdaBody() {
            Debug.Assert(_scope.GeneratorOuterScope != null);

            LambdaExpression block = (LambdaExpression)_scope.Expression;

            // 1. Create a nested code gen and emit the generator body
            LambdaCompiler ncg = CreateGeneratorLambdaCompiler(block);
            _scope.Enter(ncg);
            ncg.EmitGenerator(block);
            _scope.Exit();

            // 2. Emit the generator construction
            _scope = _scope.GeneratorOuterScope;
            _scope.Enter(this);

            bool needsClosure = _scope.IsClosure || _scope.HasEnvironment;
            EmitDelegateConstruction(ncg, typeof(GeneratorNext), needsClosure);
            _ilg.EmitNew(typeof(Generator), new Type[] { typeof(GeneratorNext) });
            EmitReturn();

            _scope.Exit();
        }

        private static string GetGeneratorMethodName(string name) {
            return name + "$g" + _GeneratorCounter++;
        }

        private void EmitGenerator(LambdaExpression block) {
            IList<YieldTarget> topTargets = _generatorInfo.TopTargets;
            Debug.Assert(topTargets != null);

            Label[] jumpTable = new Label[topTargets.Count];
            for (int i = 0; i < jumpTable.Length; i++) {
                jumpTable[i] = topTargets[i].EnsureLabel(this);
            }

            Slot router = _ilg.GetLocalTmp(typeof(int));
            EmitGetGeneratorLocation();
            router.EmitSet(_ilg);
            GotoRouter = router;
            router.EmitGet(_ilg);
            _ilg.Emit(OpCodes.Switch, jumpTable);

            // fall-through on first pass
            // yield statements will insert the needed labels after their returns

            EmitExpression(block.Body);

            // fall-through is almost always possible in generators, so this
            // is almost always needed
            EmitReturnInGenerator(null);
            Finish();

            GotoRouter = null;
            _ilg.FreeLocalTmp(router);
        }

        #endregion

        private Slot GetVariable(Expression variable) {
            return _scope.ResolveVariable(variable);
        }
    }
}
